package cn.xinfei.xdecision.manager.controller.decisionflow;

import cn.xinfei.xdecision.common.model.common.ResponseEntityBuilder;
import cn.xinfei.xdecision.common.model.common.ResponseEntityDto;
import cn.xinfei.xdecision.common.model.decisionflow.*;
import cn.xinfei.xdecision.common.model.enginex.po.EngineNode;
import cn.xinfei.xdecision.common.model.enginex.po.EngineVersion;
import cn.xinfei.xdecision.common.model.enginex.vo.ActionListVo;
import cn.xinfei.xdecision.common.model.enginex.vo.NodeVo;
import cn.xinfei.xdecision.common.model.enginex.vo.response.param.DecisionFlowOutputResponse;
import cn.xinfei.xdecision.common.model.enginex.vo.response.param.NodeTypeResponse;
import cn.xinfei.xdecision.common.model.login.SSOUser;
import cn.xinfei.xdecision.common.model.login.SessionManager;
import cn.xinfei.xdecision.common.service.enginex.EngineNodeService;
import cn.xinfei.xdecision.common.service.enginex.EngineVersionService;
import cn.xinfei.xdecision.common.utils.constant.OperateNameConst;
import cn.xinfei.xdecision.common.utils.constant.OperateTypeEnum;
import cn.xinfei.xdecision.common.utils.constant.RedisConstant;
import cn.xinfei.xdecision.common.utils.enums.enginex.EngineVersionStatusEnum;
import cn.xinfei.xdecision.common.utils.exception.ApiException;
import cn.xinfei.xdecision.common.utils.exception.enums.ErrorCodeEnum;
import cn.xinfei.xdecision.manager.web.aop.ArchivesLog;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.text.MessageFormat;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

import static cn.xinfei.xdecision.common.utils.exception.enums.ErrorCodeEnum.*;
import static cn.xinfei.xdecision.common.utils.exception.util.ApiExceptionUtil.exception;

/**
 * 决策流
 */
@Api(tags = "引擎管理-决策流")
@Controller("DecisionFlowControllerV3")
@RequestMapping("/v3/decision_flow")
@ResponseBody
@Slf4j
public class DecisionFlowController {

    @Resource
    private EngineNodeService engineNodeService;
    @Resource
    private RedissonClient redissonClient;

    @Resource
    private EngineVersionService engineVersionService;

    @PostMapping(value = "/getDecisionFlowLock")
    @ApiOperation(value = "获取引擎的编辑锁")
    @ArchivesLog(type = OperateTypeEnum.CREATE, name = OperateNameConst.DecisionFlowOpt.LOCK_ENGINE)
    public ResponseEntityDto<Boolean> getDecisionFlowLock(@RequestBody NodeListReqVo nodeListReqVo) {
        EngineVersion engineVersion =
                engineVersionService.getByEngineCodeAndVersionNo(nodeListReqVo.getEngineCode(), nodeListReqVo.getVersionNo());
        if (Objects.isNull(engineVersion)) {
            throw new ApiException(ErrorCodeEnum.ENGINE_NOT_EXISTS);
        }
        if (!Objects.equals(engineVersion.getStatus(), EngineVersionStatusEnum.NEW.getStatus())) {
            throw new ApiException(ErrorCodeEnum.ENGINE_IS_NOT_NEW);
        }
        String key = getKey(nodeListReqVo.getEngineCode(), nodeListReqVo.getVersionNo());
        RLock lock = redissonClient.getLock(key);
        if (lock.isLocked()) {
            RBucket<SSOUser> bucket = redissonClient.getBucket(key);
            SSOUser user = bucket.get();
            if (Objects.equals(user.getId(), SessionManager.getLoginUserId())) {
                return ResponseEntityBuilder.buildNormalResponse(Boolean.FALSE);
            }
            throw new ApiException(ENGINE_EDITING_OTHER.getCode(),
                    MessageFormat.format(ENGINE_EDITING_OTHER.getMessage(), user.getName()));
        }
        RBucket<SSOUser> bucket = redissonClient.getBucket(key);
        bucket.set(SessionManager.getLoginAccount(), RedisConstant.ONE_DAY, TimeUnit.DAYS);
        return ResponseEntityBuilder.buildNormalResponse(Boolean.TRUE);
    }

    @PostMapping(value = "/unlockDecisionFlowLock")
    @ApiOperation(value = "解引擎的编辑锁")
    @ArchivesLog(type = OperateTypeEnum.DELETE, name = OperateNameConst.DecisionFlowOpt.UNLOCK_ENGINE)
    public ResponseEntityDto<String> unlockDecisionFlowLock(@RequestBody NodeListReqVo nodeListReqVo) {
        String key = getKey(nodeListReqVo.getEngineCode(), nodeListReqVo.getVersionNo());
        RLock lock = redissonClient.getLock(key);
        if (lock.isLocked()) {
            RBucket<SSOUser> bucket = redissonClient.getBucket(key);
            SSOUser user = bucket.get();
            if (Objects.equals(user.getId(), SessionManager.getLoginUserId())) {
                bucket.delete();
                return ResponseEntityBuilder.buildNormalResponse("完成编辑成功！");
            }
            return ResponseEntityBuilder.buildNormalResponse("完成编辑失败！");
        }
        return ResponseEntityBuilder.buildNormalResponse("完成编辑成功！");
    }

    @GetMapping(value = "/unLock")
    @ApiOperation(value = "兜底解锁")
    public ResponseEntityDto<Boolean> unLock(@RequestParam(value = "key") String key) {
        RLock lock = redissonClient.getLock(key);
        return ResponseEntityBuilder.buildNormalResponse(lock.delete());
    }

    @PostMapping(value = "/getNodeList")
    @ApiOperation(value = "获取引擎指定版本节点信息",
            notes = "获取引擎指定版本节点信息")
    public ResponseEntityDto<NodeListRespVo> getNodeList(@RequestBody @Valid NodeListReqVo nodeListReqVo) {
        return ResponseEntityBuilder.buildNormalResponse(engineNodeService.getEngineNodeList(nodeListReqVo));
    }


    @PostMapping(value = "/save")
    @ApiOperation(value = "新增空节点")
    @ArchivesLog(type = OperateTypeEnum.CREATE, name = OperateNameConst.DecisionFlowOpt.SAVE_NODE)
    public ResponseEntityDto<NodeRespVo> save(@RequestBody @Valid NodeSaveReqVo nodeSaveReqVo) {
        return ResponseEntityBuilder.buildNormalResponse(engineNodeService.saveEngineNode(nodeSaveReqVo));
    }

    @PostMapping(value = "/getNodeConditionInputParam")
    @ApiOperation(value = "获取决策流节点条件入参", notes = "根据engine_code、version_id查询当前节点前面所有节点的输出信息")
    public ResponseEntityDto<Object> getNodeConditionInputParam(@RequestBody @Valid NodeVo nodeVo) {
        DecisionFlowOutputResponse decisionFlowOutputResponse = new DecisionFlowOutputResponse();
        List<NodeTypeResponse> nodeTypeList = engineNodeService.getPreviousNodeOutput(nodeVo);
        decisionFlowOutputResponse.setNodeTypeList(nodeTypeList);
        return ResponseEntityBuilder.buildNormalResponse(decisionFlowOutputResponse);
    }

    @PostMapping(value = "/updateNodeInfo")
    @ApiOperation(value = "更新节点属性")
    @ArchivesLog(type = OperateTypeEnum.UPDATE, name = OperateNameConst.DecisionFlowOpt.UPDATE_NODE_INFO)
    public ResponseEntityDto<EngineNode> updateNodeInfo(@RequestBody @Valid UpdateNodeInfoReqVo updateNodeInfoReqVo) {

        RLock lock = redissonClient.getLock(lockKey(updateNodeInfoReqVo.getVersionId(), updateNodeInfoReqVo.getNodeCode()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            return ResponseEntityBuilder.buildNormalResponse(engineNodeService.updateNodeInfo(updateNodeInfoReqVo));
        } finally {
            lock.unlock();
        }
    }


    @PostMapping(value = "/copy/{nodeId}")
    @ApiOperation(value = "复制节点")
    @ArchivesLog(type = OperateTypeEnum.CREATE, name = OperateNameConst.DecisionFlowOpt.COPY_NODE)
    public ResponseEntityDto<String> copy(@PathVariable Long nodeId) {
        EngineNode eNode = checkNode(nodeId);
        RLock lock = redissonClient.getLock(lockKey(eNode.getVersionId(), eNode.getNodeCode()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            return ResponseEntityBuilder.buildNormalResponse(engineNodeService.copy(nodeId));
        } finally {
            lock.unlock();
        }
    }

    @PostMapping(value = "/removeNode/{nodeId}")
    @ApiOperation(value = "删除节点")
    @ArchivesLog(type = OperateTypeEnum.DELETE, name = OperateNameConst.DecisionFlowOpt.DELETE_NODE)
    public ResponseEntityDto<Object> removeNode(@PathVariable Long nodeId) {
        EngineNode eNode = checkNode(nodeId);
        RLock lock = redissonClient.getLock(lockKey(eNode.getVersionId(), eNode.getNodeCode()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            engineNodeService.removeNode(nodeId);
            return ResponseEntityBuilder.buildNormalResponse(true);
        } finally {
            lock.unlock();
        }
    }

    @PostMapping(value = "/removeNodeList")
    @ApiOperation(value = "批量删除节点")
    @ArchivesLog(type = OperateTypeEnum.DELETE, name = OperateNameConst.DecisionFlowOpt.BATCH_DELETE_NODE)
    public ResponseEntityDto<Object> removeNodeList(@RequestBody @Valid NodeRemoveBatchReqVo removeBatchReqVo) {
        RLock lock = redissonClient.getLock(lockKey(removeBatchReqVo.getVersionId()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            engineNodeService.removeNodeAll(removeBatchReqVo.getNodeIds());
            return ResponseEntityBuilder.buildNormalResponse(null);
        } finally {
            lock.unlock();
        }
    }

    @PostMapping(value = "/nodeMove")
    @ApiOperation(value = "移动节点")
    @ArchivesLog(type = OperateTypeEnum.UPDATE, name = OperateNameConst.DecisionFlowOpt.MOVE_NODE)
    public ResponseEntityDto<Object> updatePropertyForMove(@RequestBody @Valid NodeMoveReqVo nodeVo) {
        RLock lock = redissonClient.getLock(lockKey(nodeVo.getVersionId(), nodeVo.getNodeCode()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            return ResponseEntityBuilder.buildNormalResponse(engineNodeService.updateNodeForMove(nodeVo));
        } finally {
            lock.unlock();
        }
    }

    @PostMapping(value = "/link")
    @ApiOperation(value = "节点连线")
    @ArchivesLog(type = OperateTypeEnum.UPDATE, name = OperateNameConst.DecisionFlowOpt.NODE_LINK)
    public ResponseEntityDto<Object> updateProperty(@RequestBody @Valid NodeLinkReqVo nodeLinkReqVo) {
        EngineNode sourceNode = engineNodeService.getNodeById(nodeLinkReqVo.getSourceNodeId());
        EngineNode targetNode = engineNodeService.getNodeById(nodeLinkReqVo.getTargetNodeId());

        if (sourceNode == null || targetNode == null) {
            throw new ApiException(ENGINE_NODE_LINK_ERROR);
        }

        if (!sourceNode.getVersionId().equals(targetNode.getVersionId())) {
            log.error("连线节点版本不一致,开始节点版本:{},目标节点版本:{}", sourceNode.getVersionId(), targetNode.getVersionId());
            throw exception(NODE_INFO_ERROR);
        }
        RLock lock = redissonClient.getLock(lockKey(sourceNode.getVersionId()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            return ResponseEntityBuilder.buildNormalResponse(engineNodeService.link(nodeLinkReqVo));
        } finally {
            lock.unlock();
        }
    }

    @PostMapping(value = "/removeLink")
    @ApiOperation(value = "删除连线")
    @ArchivesLog(type = OperateTypeEnum.UPDATE, name = OperateNameConst.DecisionFlowOpt.DELETE_NODE_LINK)
    public ResponseEntityDto<Object> removeLink(@RequestBody @Valid NodeLinkReqVo nodeLinkReqVo) {
        EngineNode sourceNode = engineNodeService.getNodeById(nodeLinkReqVo.getSourceNodeId());
        EngineNode targetNode = engineNodeService.getNodeById(nodeLinkReqVo.getTargetNodeId());

        if (sourceNode == null || targetNode == null) {
            throw new ApiException(ENGINE_NODE_LINK_ERROR.getCode(), ENGINE_NODE_LINK_ERROR.getMessage());
        }

        if (!sourceNode.getVersionId().equals(targetNode.getVersionId())) {
            log.error("连线节点版本不一致,开始节点版本:{},目标节点版本:{}", sourceNode.getVersionId(), targetNode.getVersionId());
            throw exception(NODE_INFO_ERROR);
        }
        RLock lock = redissonClient.getLock(lockKey(sourceNode.getVersionId()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            return ResponseEntityBuilder.buildNormalResponse(engineNodeService.removeLink(nodeLinkReqVo));
        } finally {
            lock.unlock();
        }
    }


    @PostMapping(value = "/renameNode")
    @ApiOperation(value = "修改节点名称")
    @ArchivesLog(type = OperateTypeEnum.UPDATE, name = OperateNameConst.DecisionFlowOpt.RENAME_NODE)
    public ResponseEntityDto<Object> renameNode(@RequestBody @Valid NodeRenameReqVo nodeVo) {
        RLock lock = redissonClient.getLock(lockKey(nodeVo.getVersionId(), nodeVo.getNodeCode()));
        if (!lock.tryLock()) {
            return ResponseEntityBuilder.buildErrorResponse(ErrorCodeEnum.ENGINE_NODE_UPDATE_CONCURRENT);
        }
        try {
            return ResponseEntityBuilder.buildNormalResponse(engineNodeService.renameNode(nodeVo));
        } finally {
            lock.unlock();
        }
    }

    @PostMapping(value = "/findActionListByServer/{actionServerType}")
    @ApiOperation(value = "通过某个服务查看动作列表")
    public ResponseEntityDto<List<ActionListVo>> findActionListByServerType(@PathVariable Integer actionServerType) {
        return ResponseEntityBuilder.buildNormalResponse(engineNodeService.findActionListByServerType(actionServerType));
    }


    private String lockKey(Long versionId) {
        return MessageFormat.format(RedisConstant.RISK_ENGINE_BASIC_INFO_KEY,
                RedisConstant.DECISION_FLOW,
                RedisConstant.NODE_UPDATE,
                versionId);
    }

    private String lockKey(Long versionId, String nodeCode) {
        return MessageFormat.format(RedisConstant.RISK_ENGINE_BASIC_INFO_KEY,
                RedisConstant.DECISION_FLOW,
                RedisConstant.NODE_UPDATE,
                versionId + "-" + nodeCode);
    }

    private String getKey(String engineCode, Long versionNo) {
        if (StringUtils.isEmpty(engineCode) || Objects.isNull(versionNo)) {
            throw new ApiException(PARAMS_EXCEPTION.getCode(), "引擎code和版本编号不能为空!");
        }
        return MessageFormat.format(RedisConstant.RISK_ENGINE_BASIC_INFO_KEY,
                RedisConstant.DECISION_FLOW,
                RedisConstant.DECISION_FLOW_EDIT,
                engineCode + "_" + versionNo);
    }

    private EngineNode checkNode(Long nodeId) {
        EngineNode eNode = engineNodeService.getNodeById(nodeId);
        if (Objects.isNull(eNode)) {
            throw new ApiException(ENGINE_NODE_NOT_EXISTS);
        }
        return eNode;
    }

}
